from gevent import monkey;

from dispersedledger.core.bc_mvba import BM
from dispersedledger.core.recover import RECOVER

monkey.patch_all(thread=False)

from typing import Callable
import os
import pickle
from gevent import time
from myexperiements.sockettest.make_random_tx import tx_generator
from coincurve import PrivateKey, PublicKey
from multiprocessing import Value as mpValue


def load_key(id, N):
    with open(os.getcwd() + '/keys/' + 'sPK.key', 'rb') as fp:
        sPK = pickle.load(fp)

    with open(os.getcwd() + '/keys/' + 'sPK1.key', 'rb') as fp:
        sPK1 = pickle.load(fp)

    sPK2s = []
    for i in range(N):
        with open(os.getcwd() + '/keys/' + 'sPK2-' + str(i) + '.key', 'rb') as fp:
            sPK2s.append(PublicKey(pickle.load(fp)))

    with open(os.getcwd() + '/keys/' + 'ePK.key', 'rb') as fp:
        ePK = pickle.load(fp)

    with open(os.getcwd() + '/keys/' + 'sSK-' + str(id) + '.key', 'rb') as fp:
        sSK = pickle.load(fp)

    with open(os.getcwd() + '/keys/' + 'sSK1-' + str(id) + '.key', 'rb') as fp:
        sSK1 = pickle.load(fp)

    with open(os.getcwd() + '/keys/' + 'sSK2-' + str(id) + '.key', 'rb') as fp:
        sSK2 = PrivateKey(pickle.load(fp))

    with open(os.getcwd() + '/keys/' + 'eSK-' + str(id) + '.key', 'rb') as fp:
        eSK = pickle.load(fp)

    return sPK, sPK1, sPK2s, ePK, sSK, sSK1, sSK2, eSK


class DL2Node(BM):

    def __init__(self, sid, id, S, Bfast, Bacs, N, f,
                 bft_from_server1: Callable, bft_to_client1: Callable,bft_from_server2: Callable, bft_to_client2: Callable, ready: mpValue, stop: mpValue, K=3, mode='debug', mute=False, tx_buffer=None):
        self.sPK, self.sPK1, self.sPK2s, self.ePK, self.sSK, self.sSK1, self.sSK2, self.eSK = load_key(id, N)
        #self.recv_queue = recv_q
        #self.send_queue = send_q
        self.bft_to_client1 = bft_to_client1
        self.bft_from_server1 = bft_from_server1

        self.bft_to_client2 = bft_to_client2
        self.bft_from_server2 = bft_from_server2
        self.ready = ready
        self.stop = stop
        self.mode = mode
        BM.__init__(self, sid, id, max(int(Bfast), 1), N, f,
                       self.sPK, self.sSK, self.sPK1, self.sSK1, self.sPK2s, self.sSK2,
                       send1=None, send2=None, recv=None, K=K, mute=mute)

        # Hotstuff.__init__(self, sid, id, max(S, 200), max(int(Bfast), 1), N, f, self.sPK, self.sSK, self.sPK1, self.sSK1, self.sPK2s, self.sSK2, self.ePK, self.eSK, send=None, recv=None, K=K, mute=mute)

    def prepare_bootstrap(self):
        self.logger.info('node id %d is inserting dummy payload TXs' % (self.id))
        tx = tx_generator(250)  # Set each dummy TX to be 250 Byte
        if self.mode == 'test' or 'debug': #K * max(Bfast * S, Bacs)
            k = 0
            for r in range(max(self.B * self.K, 1)):
                suffix = hex(self.id) + hex(r) + ">"
                BM.submit_tx(self, tx[:-len(suffix)] + suffix)
                # print("submit to buffer: ", tx[:-len(suffix)] + suffix)
                k += 1
                if r % 50000 == 0:
                    self.logger.info('node id %d just inserts 50000 TXs' % (self.id))
        else:
            pass
            # TODO: submit transactions through tx_buffer
        self.logger.info('node id %d completed the loading of dummy TXs' % (self.id))

    def run(self):

        pid = os.getpid()
        self.logger.info('node %d\'s starts to run consensus on process id %d' % (self.id, pid))

        # TODO：这里的send1和send2的区别是什么？
        # send1 和 recv 用于广播和共识阶段，send2 和 recv2 用于检索阶段
        self._send1 = lambda j, o: self.bft_to_client1((j, o))
        self._recv = lambda: self.bft_from_server1()
        self._send2 = lambda j, o: self.bft_to_client2((j, o))
        recv2 = lambda: self.bft_from_server2()

        # 3.生成交易，根据轮数、batchsize生成交易
        self.prepare_bootstrap()

        # TODO：self.ready.value = True是什么意思？
        while not self.ready.value:
            time.sleep(1)
            # gevent.sleep(1)

        recover = RECOVER(self.sid, self.id, self.B, self.N, self.f,
                         self.sPK, self.sSK, self.sPK1, self.sSK1, self.sPK2s, self.sSK2,
                         recv=recv2, K=self.K, mute=self.mute,logger=self.logger)

        # 是的，调用recover.start()会启动线程，并在新线程中调用recover.run()方法。
        # run()方法是Thread类的一个方法，用于定义线程的行为。RECOVER类，它继承自Thread类并覆盖了run()方法。
        # 因此，当调用recover.start()时，它将启动一个新的线程，并在该线程中执行RECOVER类中的run()
        recover.start()

        # 4.运行BFT
        self.run_bft()

        recover.join()

        self.stop.value = True
